package org.springframework.security.config.annotation.authentication.builders;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.ProviderManagerBuilder;
import org.springframework.security.config.annotation.authentication.configurers.ldap.LdapAuthenticationProviderConfigurer;
import org.springframework.security.config.annotation.authentication.configurers.provisioning.InMemoryUserDetailsManagerConfigurer;
import org.springframework.security.config.annotation.authentication.configurers.provisioning.JdbcUserDetailsManagerConfigurer;
import org.springframework.security.config.annotation.authentication.configurers.userdetails.DaoAuthenticationConfigurer;
import org.springframework.security.config.annotation.authentication.configurers.userdetails.UserDetailsAwareConfigurer;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.util.Assert;

import java.util.ArrayList;
import java.util.List;

/**
 * @author Dillon
 * @date 2024/7/6
 * @slogan 致敬大师 致敬未来的你
 * @desc security 认证构建器 构建认证逻辑
 */
public class AuthenticationManagerBuilder extends AbstractConfiguredSecurityBuilder<AuthenticationManager, AuthenticationManagerBuilder>
		implements ProviderManagerBuilder<AuthenticationManagerBuilder> {

	/**
	 * 日志对象
	 */
	private final Log logger = LogFactory.getLog(getClass());

	/**
	 * 父类认证管理器
	 */
	private AuthenticationManager parentAuthenticationManager;

	/**
	 * 认证实现类列表
	 */
	private List<AuthenticationProvider> authenticationProviders = new ArrayList<>();

	/**
	 * 认证数据查询来源接口
	 */
	private UserDetailsService defaultUserDetailsService;

	/**
	 * 认证成功是否擦除凭证
	 */
	private Boolean eraseCredentials;

	/**
	 * 认证事件发布器
	 */
	private AuthenticationEventPublisher eventPublisher;

	/**
	 * 构造函数
	 * @param objectPostProcessor 对象后置处理器
	 */
	public AuthenticationManagerBuilder(ObjectPostProcessor<Object> objectPostProcessor) {
		super(objectPostProcessor, true);
	}

	/**
	 * 设置父类认证管理器
	 * @param authenticationManager 认证管理器
	 * @return 认证构建器
	 */
	public AuthenticationManagerBuilder parentAuthenticationManager(AuthenticationManager authenticationManager) {
		// 如果父类认证管理器是默认认证实现类 ProviderManager 优先使用父类是否擦除凭证标识
		if (authenticationManager instanceof ProviderManager) {
			eraseCredentials(((ProviderManager) authenticationManager).isEraseCredentialsAfterAuthentication());
		}
		this.parentAuthenticationManager = authenticationManager;
		return this;
	}

	/**
	 * 设置事件发布器
	 * @param eventPublisher 事件发布器
	 * @return 认证构建器
	 */
	public AuthenticationManagerBuilder authenticationEventPublisher(AuthenticationEventPublisher eventPublisher) {
		Assert.notNull(eventPublisher, "认证事件发布器不能为空");
		this.eventPublisher = eventPublisher;
		return this;
	}

	/**
	 * 设置是否擦除凭证标识
	 * @param eraseCredentials 设置值
	 * @return 认证管理器
	 */
	public AuthenticationManagerBuilder eraseCredentials(boolean eraseCredentials) {
		this.eraseCredentials = eraseCredentials;
		return this;
	}

	/**
	 * 便捷配置类设置 设置使用内存作为登录认证配置
	 * @return 认证管理器
	 * @throws Exception 执行异常
	 */
	public InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder> inMemoryAuthentication() throws Exception {
		return apply(new InMemoryUserDetailsManagerConfigurer<>());
	}

	/**
	 * 便捷配置类设置 设置使用数据库作为登录认证配置
	 * @return 认证管理器
	 * @throws Exception 执行异常
	 */
	public JdbcUserDetailsManagerConfigurer<AuthenticationManagerBuilder> jdbcAuthentication() throws Exception {
		return apply(new JdbcUserDetailsManagerConfigurer<>());
	}

	/**
	 * 便捷配置类设置 设置使用数据源 数据库 内存 等作为登录认证配置
	 * @param userDetailsService 自定义的userDetailsService实现类
	 * @return userDetailsService
	 * @param <T> 认证数据查询来源接口 泛型
	 * @throws Exception 执行异常
	 */
	public <T extends UserDetailsService> DaoAuthenticationConfigurer<AuthenticationManagerBuilder, T> userDetailsService(
			T userDetailsService) throws Exception {
		this.defaultUserDetailsService = userDetailsService;
		return apply(new DaoAuthenticationConfigurer<>(userDetailsService));
	}

	/**
	 * 便捷配置类设置 设置使用Ldap作为登录认证配置
	 * @return userDetailsService
	 * @throws Exception 执行异常
	 */
	public LdapAuthenticationProviderConfigurer<AuthenticationManagerBuilder> ldapAuthentication() throws Exception {
		return apply(new LdapAuthenticationProviderConfigurer<>());
	}

	/**
	 * 配置认证管理器实现类
	 *
	 * @param authenticationProvider 认证实现类
	 * @return 认证实现类构建器
	 */
	@Override
	public AuthenticationManagerBuilder authenticationProvider(AuthenticationProvider authenticationProvider) {
		this.authenticationProviders.add(authenticationProvider);
		return this;
	}

	/**
	 * 执行构建 认证管理器对象
	 *
	 * @return 构建好的对象
	 * @throws Exception 执行有慈航
	 */
	@Override
	protected ProviderManager performBuild() throws Exception {
		// 认证实现类配置判断
		if (!isConfigured()) {
			this.logger.debug("没有配置认证实现类，返回空.");
			return null;
		}
		// 初始化 认证管理器默认实现类 ProviderManager
		ProviderManager providerManager = new ProviderManager(this.authenticationProviders, this.parentAuthenticationManager);
		// 设置登录成功是否擦除凭证标识
		if (this.eraseCredentials != null) {
			providerManager.setEraseCredentialsAfterAuthentication(this.eraseCredentials);
		}
		// 设置事件发布器
		if (this.eventPublisher != null) {
			providerManager.setAuthenticationEventPublisher(this.eventPublisher);
		}
		// 对象构建完成的后置处理扩展 自定义实现
		providerManager = postProcess(providerManager);
		return providerManager;
	}

	/**
	 * 判断认证管理器是否已配置
	 * 认证管理器列表设置认证管理器 或者 父类设置认证管理器
	 * @return 认证管理器是否已配置
	 */
	public boolean isConfigured() {
		return !this.authenticationProviders.isEmpty() || this.parentAuthenticationManager != null;
	}

	/**
	 * 获取当前设置的 userDetailService 实现类
	 * @return userDetailService 实现类
	 */
	public UserDetailsService getDefaultUserDetailsService() {
		return this.defaultUserDetailsService;
	}

	/**
	 * 设置默认认证数据查询来源接口
	 * @param configurer 配置类
	 * @return 认证管理器
	 * @param <C> 配置类泛型
	 * @throws Exception 执行异常
	 */
	private <C extends UserDetailsAwareConfigurer<AuthenticationManagerBuilder, ? extends UserDetailsService>> C apply(
			C configurer) throws Exception {
		this.defaultUserDetailsService = configurer.getUserDetailsService();
		return super.apply(configurer);
	}

}
